/*
 * jxUtils Project (2013, 2014)
 * 
 * by Denilson Edinaldo Pinto
 * 
 * LGPL
 * Copyright (C) 2014
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.jxUtils.date;

import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

import org.jxUtils.check.CheckUtil;
import org.jxUtils.i18n.I18N;
import org.jxUtils.system.SystemProperties;

/**
 * @author Denilson Edinaldo Pinto
 * @project jxUtils
 * @package org.jxUtils.date
 * @date 08/04/2013 - 11:51:07
 */
public class BrazilianCalendar extends GregorianCalendar {
	
	private static final long serialVersionUID = -7425408094814706334L;
	
	public static final BrazilianCalendar INSTANCE = new BrazilianCalendar();
	
	public static final BrazilianCalendar FIRST_YEAR_DAY = new BrazilianCalendar(INSTANCE.get(YEAR), JANUARY, 1);
	
	public static final BrazilianCalendar LAST_YEAR_DAY = new BrazilianCalendar(INSTANCE.get(YEAR), DECEMBER, 31);
	
	private static final Locale LOCALE = new Locale("pt", "BR");
	
	private Holydays holydays = null;
	
	private OtherHolydays otherHolydays = null;
	
	/**
	 * 
	 */
	public BrazilianCalendar() {
	
		super();
	}
	
	/**
	 * @param year
	 * @param month
	 * @param dayOfMonth
	 */
	public BrazilianCalendar(final int year, final int month, final int dayOfMonth) {
	
		super(year, month, dayOfMonth);
	}
	
	/**
	 * @param year
	 * @param month
	 * @param dayOfMonth
	 * @param hourOfDay
	 * @param minute
	 */
	public BrazilianCalendar(final int year, final int month, final int dayOfMonth, final int hourOfDay, final int minute) {
	
		super(year, month, dayOfMonth, hourOfDay, minute);
	}
	
	/**
	 * @param year
	 * @param month
	 * @param dayOfMonth
	 * @param hourOfDay
	 * @param minute
	 * @param second
	 */
	public BrazilianCalendar(final int year, final int month, final int dayOfMonth, final int hourOfDay, final int minute, final int second) {
	
		super(year, month, dayOfMonth, hourOfDay, minute, second);
	}
	
	/**
	 * @param aLocale
	 */
	public BrazilianCalendar(final Locale aLocale) {
	
		super(aLocale);
	}
	
	/**
	 * @param zone
	 */
	public BrazilianCalendar(final TimeZone zone) {
	
		super(zone);
	}
	
	/**
	 * @param zone
	 * @param aLocale
	 */
	public BrazilianCalendar(final TimeZone zone, final Locale aLocale) {
	
		super(zone, aLocale);
	}
	
	/**
	 * @return new BrazilianCalendar()
	 */
	public static final BrazilianCalendar getInstance() {
	
		return INSTANCE;
	}
	
	/**
	 * @return the locale
	 */
	public static final Locale getLocale() {
	
		return LOCALE;
	}
	
	/**
	 * @return Working days in the current year date range
	 */
	public int countWorkingDays() {
	
		return countWorkingDays(FIRST_YEAR_DAY.getTime(), LAST_YEAR_DAY.getTime());
	}
	
	/**
	 * @param endDate
	 * @return Working days
	 */
	public int countWorkingDays(final Calendar endDate) {
		
		return countWorkingDays(new Date(), endDate.getTime());
	}
	
	/**
	 * @param endDate
	 * @return Working days
	 */
	public int countWorkingDays(final Date endDate) {
	
		return countWorkingDays(new Date(), endDate);
	}
	
	/**
	 * @param startDate
	 * @param endDate
	 * @return Working days within the date range
	 */
	public int countWorkingDays(final Date startDate, final Date endDate) {
	
		return DateUtil.countWorkingDays(this, startDate, endDate);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(final Object obj) {
	
		if(this == obj) {
			return true;
		}
		if( !super.equals(obj)) {
			return false;
		}
		if( !(obj instanceof BrazilianCalendar)) {
			return false;
		}
		final BrazilianCalendar other = (BrazilianCalendar)obj;
		if(holydays == null) {
			if(other.holydays != null) {
				return false;
			}
		}else if( !holydays.equals(other.holydays)) {
			return false;
		}
		if(otherHolydays == null) {
			if(other.otherHolydays != null) {
				return false;
			}
		}else if( !otherHolydays.equals(other.otherHolydays)) {
			return false;
		}
		return true;
	}
	
	/**
	 * @return the holydayDates
	 */
	public Map<String, HolydayDate> getHolydayDates() {
	
		return getHolydays().getHolydays();
	}
	
	/**
	 * @return the holydays
	 */
	public Holydays getHolydays() {
	
		//
		if(CheckUtil.isNull(holydays)) {
			
			holydays = new Holydays();
		}
		
		return holydays;
	}
	
	/**
	 * @return Dia da Semana, Dia de Mês de Ano
	 */
	public String getNow() {
	
		return getDisplayName(DAY_OF_WEEK, LONG, Locale.getDefault()) + ", " + get(DAY_OF_MONTH) + " de " + getDisplayName(MONTH, LONG, Locale.getDefault()) + " de " + get(YEAR);
	}
	
	/**
	 * @return the otherHolydayDates
	 */
	public Map<String, HolydayDate> getOtherHolydayDates() {
	
		return getOtherHolydays().getHolydays();
	}
	
	/**
	 * @return the otherHolydays
	 */
	public OtherHolydays getOtherHolydays() {
	
		//
		if(CheckUtil.isNull(otherHolydays)) {
			
			otherHolydays = new OtherHolydays();
		}
		
		return otherHolydays;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
	
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + (holydays == null ? 0 : holydays.hashCode());
		result = prime * result + (otherHolydays == null ? 0 : otherHolydays.hashCode());
		return result;
	}
	
	/**
	 * @param description
	 * @return holydayDate
	 */
	public HolydayDate removeHolyday(final String description) {
	
		return getHolydays().remove(description);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
	
		return DateUtil.toString(this, getLocale());
	}
	
	/**
	 * @author Denilson Edinaldo Pinto
	 * @project jxUtils
	 * @package br.com.harue.hutils.dateutil
	 * @date 14/07/2012 - 11:39:31
	 */
	public final class HolydayDate implements Serializable {
		
		private static final long serialVersionUID = 3803209082633532455L;
		
		private final int day;
		
		private final int dayOfWeek;
		
		private final GregorianCalendar gregorianCalendar;
		
		private final int month;
		
		/**
		 * 
		 */
		public HolydayDate() {
		
			this(1, JANUARY);
		}
		
		/**
		 * @param day
		 * @param month
		 */
		public HolydayDate(final int day, final int month) {
		
			this.day = day;
			this.month = month;
			
			gregorianCalendar = new GregorianCalendar(BrazilianCalendar.this.get(YEAR), month, day);
			
			dayOfWeek = gregorianCalendar.get(DAY_OF_WEEK);
		}
		
		/**
		 * @return BrazilianCalendar.this
		 */
		private BrazilianCalendar getOuterType() {
		
			return BrazilianCalendar.this;
		}
		
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#equals(java.lang.Object)
		 */
		@Override
		public boolean equals(final Object obj) {
		
			if(this == obj) {
				return true;
			}
			if(obj == null) {
				return false;
			}
			if( !(obj instanceof HolydayDate)) {
				return false;
			}
			final HolydayDate other = (HolydayDate)obj;
			if( !getOuterType().equals(other.getOuterType())) {
				return false;
			}
			if(day != other.day) {
				return false;
			}
			if(dayOfWeek != other.dayOfWeek) {
				return false;
			}
			if(gregorianCalendar == null) {
				if(other.gregorianCalendar != null) {
					return false;
				}
			}else if( !gregorianCalendar.equals(other.gregorianCalendar)) {
				return false;
			}
			if(month != other.month) {
				return false;
			}
			return true;
		}
		
		/**
		 * @return the day
		 */
		public int getDay() {
		
			return day;
		}
		
		/**
		 * @return the dayOfWeek
		 */
		public int getDayOfWeek() {
		
			return dayOfWeek;
		}
		
		/**
		 * @return the month
		 */
		public int getMonth() {
		
			return month;
		}
		
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#hashCode()
		 */
		@Override
		public int hashCode() {
		
			final int prime = 31;
			int result = 1;
			result = prime * result + getOuterType().hashCode();
			result = prime * result + day;
			result = prime * result + dayOfWeek;
			result = prime * result + (gregorianCalendar == null ? 0 : gregorianCalendar.hashCode());
			result = prime * result + month;
			return result;
		}
		
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#toString()
		 */
		@Override
		public String toString() {
		
			return day + ", " + gregorianCalendar.getDisplayName(MONTH, LONG, Locale.getDefault()) + ", " + gregorianCalendar.getDisplayName(DAY_OF_WEEK, LONG, Locale.getDefault());
		}
	}
	
	/**
	 * @author Denilson Edinaldo Pinto
	 * @project jxUtils
	 * @package br.com.harue.hutils.dateutil
	 * @date 14/07/2012 - 11:39:39
	 */
	public class Holydays implements Serializable {
		
		private static final long serialVersionUID = 8386169494484638626L;
		
		private final Map<String, HolydayDate> holydays = new HashMap<String, HolydayDate>();
		
		/**
		 * 
		 */
		public Holydays() {
		
			final GregorianCalendar carnivalCalendar = DateUtil.getCarnivalDay(getOuterType());
			final GregorianCalendar goodFridayCalendar = DateUtil.getGoodFriDay(getOuterType());
			final GregorianCalendar easterCalendar = DateUtil.getEasterDay(getOuterType());
			final GregorianCalendar corpusChristiCalendar = DateUtil.getCorpusChristi(getOuterType());
			
			holydays.put(I18N.brazilianCalendar().confraternizacaoUniversal(), new HolydayDate(1, JANUARY));
			holydays.put(I18N.brazilianCalendar().carnaval(), new HolydayDate(carnivalCalendar.get(Calendar.DAY_OF_MONTH), carnivalCalendar.get(Calendar.MONTH)));
			holydays.put(I18N.brazilianCalendar().sextaFeiraSanta(), new HolydayDate(goodFridayCalendar.get(Calendar.DAY_OF_MONTH), goodFridayCalendar.get(Calendar.MONTH)));
			holydays.put(I18N.brazilianCalendar().pascoa(), new HolydayDate(easterCalendar.get(Calendar.DAY_OF_MONTH), easterCalendar.get(Calendar.MONTH)));
			holydays.put(I18N.brazilianCalendar().corpusChristi(), new HolydayDate(corpusChristiCalendar.get(Calendar.DAY_OF_MONTH), corpusChristiCalendar.get(Calendar.MONTH)));
			holydays.put(I18N.brazilianCalendar().tiradentes(), new HolydayDate(21, APRIL));
			holydays.put(I18N.brazilianCalendar().diaDoTrabalho(), new HolydayDate(1, MAY));
			holydays.put(I18N.brazilianCalendar().independenciaDoBrasil(), new HolydayDate(7, SEPTEMBER));
			holydays.put(I18N.brazilianCalendar().nossaSenhoraAparecida(), new HolydayDate(12, OCTOBER));
			holydays.put(I18N.brazilianCalendar().finados(), new HolydayDate(2, NOVEMBER));
			holydays.put(I18N.brazilianCalendar().proclamacaoDaRepublica(), new HolydayDate(15, NOVEMBER));
			holydays.put(I18N.brazilianCalendar().natal(), new HolydayDate(25, DECEMBER));
		}
		
		/**
		 * @return BrazilianCalendar.this
		 */
		private BrazilianCalendar getOuterType() {
		
			return BrazilianCalendar.this;
		}
		
		/**
		 * @param description
		 * @param holydayDate
		 */
		void add(final String description, final HolydayDate holydayDate) {
		
			holydays.put(description, holydayDate);
		}
		
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#equals(java.lang.Object)
		 */
		@Override
		public boolean equals(final Object obj) {
		
			if(this == obj) {
				return true;
			}
			if(obj == null) {
				return false;
			}
			if( !(obj instanceof Holydays)) {
				return false;
			}
			final Holydays other = (Holydays)obj;
			if( !getOuterType().equals(other.getOuterType())) {
				return false;
			}
			if(holydays == null) {
				if(other.holydays != null) {
					return false;
				}
			}else if( !holydays.equals(other.holydays)) {
				return false;
			}
			return true;
		}
		
		/**
		 * @return the holydays
		 */
		public Map<String, HolydayDate> getHolydays() {
		
			return holydays;
		}
		
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#hashCode()
		 */
		@Override
		public int hashCode() {
		
			final int prime = 31;
			int result = 1;
			result = prime * result + getOuterType().hashCode();
			result = prime * result + (holydays == null ? 0 : holydays.hashCode());
			return result;
		}
		
		/**
		 * @param description
		 * @return holydayDate
		 */
		public HolydayDate remove(final String description) {
		
			return holydays.remove(description);
		}
		
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#toString()
		 */
		@Override
		public String toString() {
		
			final StringBuilder builder = new StringBuilder("");
			
			//
			for(final String description : holydays.keySet()) {
				
				builder.append(description + " - " + holydays.get(description) + SystemProperties.getLineSeparator());
			}
			
			return builder.toString();
		}
	}
	
	/**
	 * @author Denilson Edinaldo Pinto
	 * @project jxUtils
	 * @package org.jxUtils.date
	 * @date 04/05/2013 - 18:27:24
	 */
	public class OtherHolydays implements Serializable {
		
		private static final long serialVersionUID = 8386169494484638626L;
		
		private final Map<String, HolydayDate> holydays = new HashMap<String, HolydayDate>();
		
		/**
		 * 
		 */
		public OtherHolydays() {
		
			final GregorianCalendar ashWednesDay = DateUtil.getAshWednesDay(getOuterType());
			final GregorianCalendar pentecostDay = DateUtil.getPentecostDay(getOuterType());
			
			holydays.put(I18N.brazilianCalendar().diaDosNamorados(), new HolydayDate(12, JUNE));
			holydays.put(I18N.brazilianCalendar().diaDoProfessor(), new HolydayDate(15, OCTOBER));
			holydays.put(I18N.brazilianCalendar().concienciaNegra(), new HolydayDate(20, NOVEMBER));
			holydays.put(I18N.brazilianCalendar().quartaFeiraDeCinzas(), new HolydayDate(ashWednesDay.get(Calendar.DAY_OF_MONTH), ashWednesDay.get(Calendar.MONTH)));
			holydays.put(I18N.brazilianCalendar().pentecostes(), new HolydayDate(pentecostDay.get(Calendar.DAY_OF_MONTH), pentecostDay.get(Calendar.MONTH)));
		}
		
		/**
		 * @return BrazilianCalendar.this
		 */
		private BrazilianCalendar getOuterType() {
		
			return BrazilianCalendar.this;
		}
		
		/**
		 * @param description
		 * @param holydayDate
		 */
		void add(final String description, final HolydayDate holydayDate) {
		
			holydays.put(description, holydayDate);
		}
		
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#equals(java.lang.Object)
		 */
		@Override
		public boolean equals(final Object obj) {
		
			if(this == obj) {
				return true;
			}
			if(obj == null) {
				return false;
			}
			if( !(obj instanceof Holydays)) {
				return false;
			}
			final Holydays other = (Holydays)obj;
			if( !getOuterType().equals(other.getOuterType())) {
				return false;
			}
			if(holydays == null) {
				if(other.holydays != null) {
					return false;
				}
			}else if( !holydays.equals(other.holydays)) {
				return false;
			}
			return true;
		}
		
		/**
		 * @return the holydays
		 */
		public Map<String, HolydayDate> getHolydays() {
		
			return holydays;
		}
		
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#hashCode()
		 */
		@Override
		public int hashCode() {
		
			final int prime = 31;
			int result = 1;
			result = prime * result + getOuterType().hashCode();
			result = prime * result + (holydays == null ? 0 : holydays.hashCode());
			return result;
		}
		
		/**
		 * @param description
		 * @return holydayDate
		 */
		public HolydayDate remove(final String description) {
		
			return holydays.remove(description);
		}
		
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#toString()
		 */
		@Override
		public String toString() {
		
			final StringBuilder builder = new StringBuilder("");
			
			//
			for(final String description : holydays.keySet()) {
				
				builder.append(description + " - " + holydays.get(description) + SystemProperties.getLineSeparator());
			}
			
			return builder.toString();
		}
	}
}
